#include "nnslitebufferedheapfile.h"
#include "nnslitememory.h"

static NNSLITE_RET FillReadBuffer(nnsliteBufferedHeapFile* bhf);

NNSLITE_RET nnsliteBufferedHeapFileOpenNew(nnsliteHeapFS* heapFS, nnsliteBufferedHeapFile* bhf, u64 bufsize, u32 hfd)
{
	NNSLITE_RET ret;
	/* Create the primitive heap file */
	ret = nnsliteHeapFileOpenNew(heapFS, &bhf->hf, hfd);
	NNSLITE_ON_FAILED_RETURN(ret);
	/* Allocate the memory for the writing buffer */
	bhf->wbuf = (u8*)nnsliteMAlloc(bufsize);
	if (bhf->wbuf == NULL)
	{
		assert(FALSE);
		return NNSLITE_RET_ERR_ALLOCATE_MEM;
	}
	/* Allocate the memory for the reading buffer */
	bhf->rbuf = (u8*)nnsliteMAlloc(bufsize);
	if (bhf->rbuf == NULL)
	{
		assert(FALSE);
		return NNSLITE_RET_ERR_ALLOCATE_MEM;
	}
	/* Initialize the fields of the buffered heap file structure */
	bhf->wbufcapacity = bufsize;
	bhf->wbufsize = 0;
	bhf->wbufaddr = 0;
	bhf->rbufcapacity = bufsize;
	bhf->rbufendpos = 0;
	bhf->rbufstartpos = 0;
	bhf->rbufaddr = 0;

	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteBufferedHeapFileOpen(nnsliteHeapFS* heapFS, u32 hfd, nnsliteBufferedHeapFile* bhf, u64 bufsize)
{
	NNSLITE_RET ret;
	/* Create the primitive heap file */
	ret = nnsliteHeapFileOpen(heapFS, hfd, &bhf->hf);
	NNSLITE_ON_FAILED_RETURN(ret);
	/* Allocate the memory for the writing buffer */
	bhf->wbuf = (u8*)nnsliteMAlloc(bufsize);
	if (bhf->wbuf == NULL)
	{
		assert(FALSE);
		return NNSLITE_RET_ERR_ALLOCATE_MEM;
	}
	/* Allocate the memory for the reading buffer */
	bhf->rbuf = (u8*)nnsliteMAlloc(bufsize);
	if (bhf->rbuf == NULL)
	{
		assert(FALSE);
		return NNSLITE_RET_ERR_ALLOCATE_MEM;
	}
	/* Initialize the fields of the buffered heap file structure */
	bhf->wbufcapacity = bufsize;
	bhf->wbufsize = 0;
	bhf->wbufaddr = 0;
	bhf->rbufcapacity = bufsize;
	bhf->rbufendpos = 0;
	bhf->rbufstartpos = 0;
	bhf->rbufaddr = 0;

	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteBufferedHeapFileClose(nnsliteBufferedHeapFile* bhf)
{
	NNSLITE_RET ret;
	/* Flush the writing buffer */
	ret = nnsliteBufferedHeapFileFlush(bhf);
	NNSLITE_ON_FAILED_RETURN(ret);
	/* Close the primative file */
	ret = nnsliteHeapFileClose(bhf->hf);
	NNSLITE_ON_FAILED_RETURN(ret);
	/* Free the memories of buffers */
	if (bhf->wbuf != NULL)
		nnsliteMFree(bhf->wbuf);
	bhf->wbuf = NULL;
	if (bhf->rbuf != NULL)
		nnsliteMFree(bhf->rbuf);
	bhf->rbuf = NULL;
	/* Reset the fields of the buffered heap file structure */
	bhf->wbufsize = 0;
	bhf->wbufcapacity = 0;
	bhf->rbufendpos = 0;
	bhf->rbufstartpos = 0;
	bhf->hf = NULL;
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteBufferedHeapFileDelete(nnsliteBufferedHeapFile* bhf)
{
	NNSLITE_RET ret;
	ret = nnsliteBufferedHeapFileClose(bhf);
	NNSLITE_ON_FAILED_RETURN(ret);
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteBufferedHeapFileWrite(nnsliteBufferedHeapFile* bhf, const void* buf, u64 bufsize)
{
	NNSLITE_RET ret;
	const u8* byteBuf = (const u8*)buf;
	u64 freeBufSize = bhf->wbufcapacity-bhf->wbufsize;
	while (freeBufSize < bufsize)
	{
		if (freeBufSize > 0) 
		{
			/* Make the write buffer full */
			memcpy(bhf->wbuf+bhf->wbufsize, byteBuf, freeBufSize);
			byteBuf += freeBufSize;
			bhf->wbufsize += freeBufSize;
			bufsize -= freeBufSize;
			freeBufSize = 0;
		}
		/* Write the buffer to heap file */
		ret = nnsliteBufferedHeapFileFlush(bhf);
		NNSLITE_ON_FAILED_RETURN(ret);
		/* Update the free size of buffer */
		freeBufSize = bhf->wbufcapacity-bhf->wbufsize;
	}
	/* Append the data to the write buffer*/
	memcpy(bhf->wbuf+bhf->wbufsize, byteBuf, bufsize);
	bhf->wbufsize += bufsize;
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteBufferedHeapFileFlush(nnsliteBufferedHeapFile* bhf)
{
	if (bhf->wbufsize > 0)
	{
		/* Write the buffer to heap file */
		nnsliteHeapFileAppend(bhf->hf, bhf->wbuf, bhf->wbufsize);
		/* Clear the buffer and Update the buffer address */
		bhf->wbufaddr += bhf->wbufsize;
		bhf->wbufsize = 0;
	}
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteBufferedHeapFileBeginRead(nnsliteBufferedHeapFile* bhf)
{
	/* Clear the reading buffer */
	bhf->rbufaddr = 0;
	bhf->rbufendpos = 0;
	bhf->rbufstartpos = 0;
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteBufferedHeapFileRead(nnsliteBufferedHeapFile* bhf, void* buf, u64 bufsize)
{
	NNSLITE_RET ret;
	u8* byteBuf = (u8*)buf;
	u64 sizeCanRead;

	/* Calculate the buffered data size */
	if (bhf->rbufendpos >= bhf->rbufstartpos)
		sizeCanRead = bhf->rbufendpos - bhf->rbufstartpos;
	else
		sizeCanRead = bhf->rbufcapacity - bhf->rbufstartpos;

	/* If it has no more data buffered, it needs to read data from primitive heap file */
	if (sizeCanRead == 0)
	{
		ret = FillReadBuffer(bhf);
		NNSLITE_ON_FAILED_RETURN(ret);
		sizeCanRead = bhf->rbufendpos - bhf->rbufstartpos;
	}
	while (sizeCanRead < bufsize)
	{
		/* Copy the buffered data to destination address */
		memcpy(byteBuf, bhf->rbuf+bhf->rbufstartpos, sizeCanRead);
		byteBuf += sizeCanRead;
		bufsize -= sizeCanRead;
		bhf->rbufstartpos += sizeCanRead;
		if (bhf->rbufstartpos == bhf->rbufcapacity)
		{
			bhf->rbufstartpos = 0;
			if (bhf->rbufendpos == bhf->rbufcapacity)
				bhf->rbufendpos = 0;
		}
		/* Calculate the buffered data size */
		if (bhf->rbufendpos >= bhf->rbufstartpos)
			sizeCanRead = bhf->rbufendpos - bhf->rbufstartpos;
		else
			sizeCanRead = bhf->rbufcapacity - bhf->rbufstartpos;
		/* If it has no more data buffered, it needs to read data from primitive heap file */
		if (sizeCanRead == 0)
		{
			ret = FillReadBuffer(bhf);
			NNSLITE_ON_FAILED_RETURN(ret);
			sizeCanRead = bhf->rbufendpos - bhf->rbufstartpos;
		}
	}
	/* Copy the rest data to the destination address */
	memcpy(byteBuf, bhf->rbuf+bhf->rbufstartpos, bufsize);
	byteBuf += bufsize;
	bhf->rbufstartpos += bufsize;
	if (bhf->rbufstartpos == bhf->rbufcapacity)
	{
		bhf->rbufstartpos = 0;
		if (bhf->rbufendpos == bhf->rbufcapacity)
			bhf->rbufendpos = 0;
	}

	return NNSLITE_RET_OK;
}

static NNSLITE_RET FillReadBuffer(nnsliteBufferedHeapFile* bhf)
{
	NNSLITE_RET ret;
	u64 sizeCanRead;
	u64 filelen = bhf->hf->length;
	if (filelen <= bhf->rbufaddr) /* No more file data from primitive heap file can read */
	{
		/* Read data from write buffer */
		sizeCanRead = MIN(bhf->wbufaddr+bhf->wbufsize-bhf->rbufaddr, bhf->rbufcapacity);
		if (sizeCanRead == 0) /* No data can be read from writing buffer */
			return NNSLITE_RET_ERR_NO_AVAILABLE;
		memcpy(bhf->rbuf, bhf->wbuf+bhf->wbufaddr - bhf->rbufaddr, sizeCanRead);
	}
	else
	{
		/* Read data from primitive heap file */
		u64 leftFileSize = filelen - bhf->rbufaddr;
		sizeCanRead = MIN(leftFileSize, bhf->rbufcapacity);
		ret = nnsliteHeapFileRead(bhf->hf, bhf->rbufaddr, bhf->rbuf, sizeCanRead);
		NNSLITE_ON_FAILED_RETURN(ret);
	}
	/* Update the reading buffer */
	bhf->rbufaddr += sizeCanRead;
	bhf->rbufendpos = sizeCanRead;
	bhf->rbufstartpos = 0;
	return NNSLITE_RET_OK;
}

